/*****************************************************************************
 *  FFTerm - Simple & Platform independent, Thread-Safe Terminal/Console     *
 *  Copyright (C) 2009  James Walmsley (james@worm.me.uk)                    *
 *                                                                           *
 *  This program is free software: you can redistribute it and/or modify     *
 *  it under the terms of the GNU General Public License as published by     *
 *  the Free Software Foundation, either version 3 of the License, or        *
 *  (at your option) any later version.                                      *
 *                                                                           *
 *  This program is distributed in the hope that it will be useful,          *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
 *  GNU General Public License for more details.                             *
 *                                                                           *
 *  You should have received a copy of the GNU General Public License        *
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.    *
 *                                                                           *
 *  IMPORTANT NOTICE:                                                        *
 *  =================                                                        *
 *  Alternative Licensing is available directly from the Copyright holder,   *
 *  (James Walmsley). For more information consult LICENSING.TXT to obtain   *
 *  a Commercial license.                                                    *
 *                                                                           *
 *  See RESTRICTIONS.TXT for extra restrictions on the use of FFTerm.        *
 *                                                                           *
 *  Removing the above notice is illegal and will invalidate this license.   *
 *****************************************************************************
 *  See http://worm.me.uk/ffterm for more information.                       *
 *  Or  http://ffterm.googlecode.com/ for latest releases and the wiki.      *
 *****************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "ffterm.h"

#define _FFTERM_CR_CHAR_	-1
#define _FFTERM_LF_CHAR_	-2
#define _FFTERM_DEAD_CHAR_	-4096

/**
 *	@file		FFTerm.c
 *	@author		James Walmsley
 *	@ingroup	FFTERM
 *
 *	@defgroup	FFTERM	FFTerm
 *	@brief		A platform independent console/terminal program.
 *
 *	Provides a simple and extendable terminals running over multiple, thread-safe
 *	independent consoles.
 *
 **/

static FFT_COMMAND	*FFTerm_GetCmd				(FFT_CONSOLE *pConsole, const FF_T_INT8 *pa_cmdName, FF_T_SINT32 *pError);
static FF_T_SINT32	 FFTerm_PrintPrompt			(FFT_CONSOLE *pConsole);
static FF_T_SINT32	 FFTerm_SortCommands		(FFT_CONSOLE *pConsole);
static FF_T_SINT32	 FFTerm_GetCommandLine		(FFT_CONSOLE *pConsole, FF_T_INT8 *strCommandLine, FF_T_UINT32 ulLimit);
static FF_T_SINT32	 FFTerm_GenerateArguments	(FF_T_INT8 *strCmdLine, FF_T_INT8 **ppArgv);
static void			 FFTerm_tolower				(FF_T_INT8 *string);
static FF_T_BOOL	 FFTerm_isSpace				(FF_T_INT8 c);


static FFT_BGJOBS	g_oJobsTable[FFT_MAX_BG_JOBS];
static FF_T_BOOL	bJobsTableInit = FF_FALSE;
static FF_T_UINT16	g_usPid = 0;

/**
 *	@public
 *	@brief	Creates a console for the FFTerm Terminal.
 *
 *	@param	pa_strCmdPrompt		A string containing the command-prompt.
 *	@param	pa_pStdIn			FILE stream pointer for the INPUT for this console.
 *	@param	pa_pStdOut			FILE stream pointer for the OUTPUT for this console.
 *	@param	pError				Pointer to an Error code integer. Can be NULL when not checking errors.
 *	@param	pError				A string describing the Error code can be got by calling FFTerm_GetErrMessage()
 *
 *	@return	A valid console pointer on success, or NULL on any error. The value of pError can be checked for more
 *	@return	information about the specified error.
 *
 *	@see	FFTerm_GetErrMessage()
 *
 **/
FFT_CONSOLE *FFTerm_CreateConsole(FF_T_INT8 *pa_strCmdPrompt, FILE *pa_pStdIn, FILE * pa_pStdOut, FF_T_SINT32 *pError) {
	FFT_CONSOLE *pConsole;
	
	if(pError) {
		*pError = FFT_ERR_NONE;	// Initialise the Error to no error.
	}

	if(strlen(pa_strCmdPrompt) > FFT_MAX_CMD_PROMPT) {
		if(pError) {
			*pError = FFT_ERR_CMD_PROMPT_TOO_LONG;
		}

		return (FFT_CONSOLE *) NULL;
	}

	pConsole = (FFT_CONSOLE *) malloc(sizeof(FFT_CONSOLE));
	if(!pConsole) {
		if(pError) {
			*pError = (FFT_ERR_NOT_ENOUGH_MEMORY);
		}
		return (FFT_CONSOLE *) NULL;
	}

	pConsole->pCommands		= (FFT_COMMAND *) NULL;
	pConsole->bKill			= FF_FALSE;
	pConsole->pStdIn		= pa_pStdIn;
	pConsole->pStdOut		= pa_pStdOut;
	pConsole->ulMode		= FFT_MODE_DEFAULT;
//	pConsole->cCRLF[0]		= (FF_T_SINT32) FFT_RETURN;
//	pConsole->cCRLF[1]		= (FF_T_SINT32) _FFTERM_DEAD_CHAR_;
	pConsole->pTabulate		= NULL;
	strcpy(pConsole->strCmdPrompt, pa_strCmdPrompt);
	
	return pConsole;
}

FF_T_SINT32 FFTerm_RegisterPlatformSpecification(FFT_CONSOLE *pConsole, FFT_PLATFORMSPEC *pSpec) {
	if(!pConsole) {
		return -1; //FF_ERR_NULL_POINTER;
	}

	pConsole->pSpec = pSpec;

	return 0; //FF_ERR_NONE;
}


/**
 *	@public
 *	@brief	Adds a simple command to the provided FFTerm console.
 *
 *	Add's commands with functions of the form:
 *	int cmd_function(int argc, char **argv) {
 *		// Command code goes here.
 *		// Just like the main() function of a standard C program.
 *	}	
 *
 *	@param	pConsole		The FFT_CONSOLE object pointer.
 *	@param	pa_cmdName		A string of the command name.
 *	@param	pa_fnCmd		Function pointer to a command handler. (The name of the function for this command).
 *	@param	pa_ErrTable		A pointer to an Error Code Table. This can be NULL.
 *	@param	pa_ErrTable		See the documentation regarding FFT_ERR_TABLE definitions.
 *
 *	@return	FFT_ERR_NONE on Success, a negative error code on failure.
 *	@return	A string describing the Error code can be got by calling FFTerm_GetErrMessage().
 *
 **/
FF_T_SINT32 FFTerm_AddCmd(FFT_CONSOLE *pConsole, const FF_T_INT8 *pa_cmdName, FFT_FN_COMMAND pa_fnCmd, const FFT_ERR_TABLE *pa_ErrTable) {
	
	FFT_COMMAND *pCommand;

	if(!pConsole) {
		return FFT_ERR_NULL_POINTER;
	}

	if(strlen(pa_cmdName) > FFT_MAX_CMD_NAME) {
		return FFT_ERR_CMD_NAME_TOO_LONG;
	}

	if(!FFTerm_GetCmd(pConsole, pa_cmdName, NULL)) {
		
		if(pConsole->pCommands == NULL) {
			pConsole->pCommands = (FFT_COMMAND *) malloc(sizeof(FFT_COMMAND));
			pCommand = pConsole->pCommands;
		} else {
			pCommand = pConsole->pCommands;
			while(pCommand->pNextCmd != NULL) {	// Traverse to the end of the commands list.
				pCommand = pCommand->pNextCmd;
			}
			pCommand->pNextCmd = (FFT_COMMAND *) malloc(sizeof(FFT_COMMAND));
			pCommand = pCommand->pNextCmd;
		}
		
		if(pCommand) {
			pCommand->pNextCmd	= (FFT_COMMAND *) NULL;
			pCommand->fnCmd		= pa_fnCmd;
			pCommand->fnCmdEx	= NULL;
			pCommand->ErrTable  = pa_ErrTable;
			strcpy(pCommand->cmdName, pa_cmdName);

			FFTerm_SortCommands(pConsole);	// Keep commands sorted in Alphanumeric order.
		}

		return FFT_ERR_NONE;
	}

	return FFT_ERR_CMD_ALREADY_EXISTS;
}

/**
 *	@public
 *	@brief	Adds an extended command to the provided FFTerm console.
 *
 *	Add's commands with functions of the form:
 *	int cmd_function(int argc, char **argv, void *pParam) {
 *		// Command code goes here.
 *		// pParam is passed as an argument.
 *	}
 *
 *	@param	pConsole		The FFT_CONSOLE object pointer.
 *	@param	pa_cmdName		A string of the command name.
 *	@param	pa_fnCmd		Function pointer to a command handler. (The name of the function for this command).
 *	@param	pa_ErrTable		A pointer to an Error Code Table. This can be NULL.
 *	@param	pa_ErrTable		See the documentation regarding FFT_ERR_TABLE definitions.
 *	@param	pParam			A pointer to anything that the command might need.
 *
 *	@return	FFT_ERR_NONE on Success, a negative error code on failure.
 *	@return	A string describing the Error code can be got by calling FFTerm_GetErrMessage().
 *
 **/
FF_T_SINT32 FFTerm_AddExCmd(FFT_CONSOLE *pConsole, const FF_T_INT8 *pa_cmdName, FFT_FN_COMMAND_EX pa_fnCmd, const FFT_ERR_TABLE *pa_ErrTable, void *pParam) {
	
	FFT_COMMAND *pCommand;

	if(!pConsole) {
		return FFT_ERR_NULL_POINTER;
	}

	if(strlen(pa_cmdName) > FFT_MAX_CMD_NAME) {
		return FFT_ERR_CMD_NAME_TOO_LONG;
	}

	if(!FFTerm_GetCmd(pConsole, pa_cmdName, NULL)) {
		
		if(pConsole->pCommands == NULL) {
			pConsole->pCommands = (FFT_COMMAND *) malloc(sizeof(FFT_COMMAND));
			pCommand = pConsole->pCommands;
		} else {
			pCommand = pConsole->pCommands;
			while(pCommand->pNextCmd != NULL) {	// Traverse to the end of the commands list.
				pCommand = pCommand->pNextCmd;
			}
			pCommand->pNextCmd = (FFT_COMMAND *) malloc(sizeof(FFT_COMMAND));
			pCommand = pCommand->pNextCmd;
		}
		
		if(pCommand) {
			pCommand->pNextCmd	= (FFT_COMMAND *) NULL;
			pCommand->fnCmdEx	= pa_fnCmd;
			pCommand->fnCmd		= NULL;
			pCommand->ErrTable  = pa_ErrTable;
			pCommand->CmdExParam = pParam;
			strcpy(pCommand->cmdName, pa_cmdName);
			FFTerm_SortCommands(pConsole);	// Keep commands sorted in Alphanumeric order.
		}

		return FFT_ERR_NONE;
	}

	return FFT_ERR_CMD_ALREADY_EXISTS;
}

/**
 *	@public
 *	@brief	Removes a command from the specified FFTerm console.
 *
 *	@param	pConsole		The FFT_CONSOLE object pointer.
 *	@param	pa_cmdName		String of the command to remove.
 *
 **/
FF_T_SINT32 FFTerm_RemoveCmd(FFT_CONSOLE *pConsole, const FF_T_INT8 *pa_cmdName) {

	FFT_COMMAND *pCommand;
	FFT_COMMAND *pRmCmd;
	FF_T_SINT32	Error = FFT_ERR_NONE;
	
	if(!pConsole) {
		return FFT_ERR_NULL_POINTER;
	}

	pCommand = pConsole->pCommands;
	pRmCmd = FFTerm_GetCmd(pConsole, pa_cmdName, &Error);

	if(Error) {
		return Error;
	}

	if(pRmCmd) {

		if(pCommand == pRmCmd) {
			pConsole->pCommands = pRmCmd->pNextCmd;
			free(pRmCmd);
			return FFT_ERR_NONE;
		}

		while(pCommand != NULL) {
			if(pCommand->pNextCmd == pRmCmd) {
				pCommand->pNextCmd = pRmCmd->pNextCmd;
				free(pRmCmd);
				return FFT_ERR_NONE;
			}
			pCommand = pCommand->pNextCmd;
		}
	}

	return FFT_ERR_CMD_NOT_FOUND;
}

/**
 *	@public
 *	@brief	Sets the Mode of the specified console.
 *
 **/
FF_T_SINT32 FFTerm_SetConsoleMode(FFT_CONSOLE *pConsole, FF_T_UINT32 Mode) {
	if(!pConsole) {
		return FFT_ERR_NULL_POINTER;
	}

	pConsole->ulMode = Mode;

	return FFT_ERR_NONE;
}

/**
 *	@public
 *	@brief	Gets the Mode of the specified console.
 *
 **/
FF_T_SINT32 FFTerm_GetConsoleMode(FFT_CONSOLE *pConsole, FF_T_UINT32 *Mode) {
	if(!pConsole) {
		return FFT_ERR_NULL_POINTER;
	}

	if(Mode) {
		*Mode = pConsole->ulMode;
		return FFT_ERR_NONE;
	}

	return FFT_ERR_NULL_POINTER;
}


/**
 *	@public
 *	@brief	Starts the specified Console.
 *
 *	@param	pConsole		The FFT_CONSOLE object pointer.
 *
 *	@return	FFT_ERR_NONE on Success.
 **/
FF_T_SINT32 FFTerm_StartConsole(FFT_CONSOLE *pConsole) {

	FF_T_INT8 CmdLineBuffer[FFT_MAX_CMD_LINE_INPUT];
	FF_T_UINT16 i;

	if(!pConsole) {
		return FFT_ERR_NULL_POINTER;
	}

	if(pConsole->pSpec && pConsole->pSpec->pfnOnCreate) {
		pConsole->pSpec->pfnOnCreate();
	}

	FFTerm_HookDefaultCommands(pConsole);	// Hook the default commands.

	while(pConsole->bKill != FF_TRUE) {
		
		FFTerm_PrintPrompt(pConsole);
		
		// Get Command Line
		FFTerm_GetCommandLine(pConsole, CmdLineBuffer, FFT_MAX_CMD_LINE_INPUT);

		for(i = 0; i < FFT_MAX_BG_JOBS; i++) {
			if(g_oJobsTable[i].bInUse && g_oJobsTable[i].bExecuted) {
				fprintf(pConsole->pStdOut, "[%d]+ Done - (%s)\n", g_oJobsTable[i].usPid, g_oJobsTable[i].threadParams.strCommandLine);
				g_oJobsTable[i].bInUse = 0;
				g_oJobsTable[i].bExecuted = 0;
			}
		}

		// If command line ends with a '&' then execute this on a new background thread.
		// Allowing other commands to execute concurrently :D
		for(i = (FF_T_UINT16) strlen(CmdLineBuffer); i > 0; i--) {
			if(!FFTerm_isSpace(CmdLineBuffer[i-1])) {
				if(CmdLineBuffer[i-1] == '&') {
					CmdLineBuffer[i-1] = '\0';
					if(FFTerm_isSpace(CmdLineBuffer[i-2])) {
						CmdLineBuffer[i-2] = '\0';
					}
					FFTerm_ExecCommandInBG(pConsole, CmdLineBuffer);
					goto skipExec;
				}
			}
			if(CmdLineBuffer[i-1] != '&') {
				break;
			}
		}

		FFTerm_ExecCommand(pConsole, CmdLineBuffer);

skipExec:
		;
	}

	fprintf(pConsole->pStdOut, "Kill Signal Received - Closing Console\n");

	if(pConsole->pSpec && pConsole->pSpec->pfnOnExit) {
		pConsole->pSpec->pfnOnExit();
	}
	
	return FFT_ERR_NONE;
}


FF_T_SINT32	FFTerm_ExecCommand(FFT_CONSOLE *pConsole, FF_T_INT8 *strCommand) {
	
	FFT_COMMAND *pCommand;
	FF_T_SINT32	Error = FFT_ERR_NONE;
	FF_T_SINT32 Result = FFT_ERR_NONE;
	FF_T_SINT32	Argc;
	FF_T_INT8	*pArgs[FFT_MAX_CMD_LINE_ARGS];
	FF_T_INT8	strCommandModifiable[FFT_MAX_CMD_LINE_INPUT];

	strcpy(strCommandModifiable, strCommand);

	// Process Command Line into Arguments
	Argc = FFTerm_GenerateArguments(strCommandModifiable, (FF_T_INT8**)pArgs);

	pCommand = FFTerm_GetCmd(pConsole, pArgs[0], &Error);
	
	if(pCommand) {
		pArgs[0] = pCommand->cmdName;	// Ensure argv[0] is formatted like the registered command name.
		if(pCommand->fnCmd || pCommand->fnCmdEx) {
			if(pCommand->fnCmd) {
				// Execute the relevant command!
				Result = (FF_T_SINT32) pCommand->fnCmd((int)Argc, pArgs);
				if(Result == FFT_KILL_CONSOLE) {
					pConsole->bKill = FF_TRUE;
					//break;
				}
			} else if (pCommand->fnCmdEx) {
				Result = (FF_T_SINT32) pCommand->fnCmdEx((int)Argc, pArgs, pCommand->CmdExParam);
				if(Result == FFT_KILL_CONSOLE) {
					pConsole->bKill = FF_TRUE;
					//break;
				}
			} else {
				Result = 0;
				fprintf(pConsole->pStdOut, "A command was found, but no registered executable address was found.\n");
			}

			if(Result) {
				if(pCommand->ErrTable) {
					fprintf(pConsole->pStdOut, "Command returned with message: \"%s\"\n", FFTerm_LookupErrMessage(pCommand->ErrTable, Result));
				} else {
					fprintf(pConsole->pStdOut, "Command returned with Code (%ld)\n", Result);
				}
			}
		}
	} else {
		
		if(Error == FFT_ERR_CMD_NOT_FOUND) {
			if(strlen(strCommand) > 0) {
				fprintf(pConsole->pStdOut, "'%s' is not recognised as an internal or external command.\n", pArgs[0]);
			}
		} else {
			if(strlen(strCommand) > 0) {
				fprintf(pConsole->pStdOut, "%s\n", FFTerm_GetErrMessage(Error));
			}
		}
	}

	return Result;

}


FF_T_SINT32 bgThread(void *pParam) {
	FFT_COMMAND *pCommand;
	FF_T_SINT32	Error = FFT_ERR_NONE;
	FF_T_SINT32 Result = FFT_ERR_NONE;
	//FF_T_SINT32	Argc;
	//FF_T_INT8	*pArgs[FFT_MAX_CMD_LINE_ARGS];
	//FF_T_INT8	strCommandModifiable[FFT_MAX_CMD_LINE_INPUT];
	FFT_BGJOBS *pJobEntry = (FFT_BGJOBS *) pParam;
	FFT_THREADPARAMS *threadParams = (FFT_THREADPARAMS *) &pJobEntry->threadParams;

	//strcpy(strCommandModifiable, strCommand);

	// Process Command Line into Arguments
	threadParams->Argc = FFTerm_GenerateArguments(threadParams->strCommandLine, (FF_T_INT8**)threadParams->pArgs);

	pCommand = FFTerm_GetCmd(threadParams->pConsole, threadParams->pArgs[0], &Error);
	
	if(pCommand) {
		threadParams->pArgs[0] = pCommand->cmdName;	// Ensure argv[0] is formatted like the registered command name.
		if(pCommand->fnCmd || pCommand->fnCmdEx) {
			if(pCommand->fnCmd) {
				// Execute the relevant command!
				Result = (FF_T_SINT32) pCommand->fnCmd((int)threadParams->Argc, threadParams->pArgs);
				if(Result == FFT_KILL_CONSOLE) {
					threadParams->pConsole->bKill = FF_TRUE;
					//break;
				}
			} else if (pCommand->fnCmdEx) {
				Result = (FF_T_SINT32) pCommand->fnCmdEx((int)threadParams->Argc, threadParams->pArgs, pCommand->CmdExParam);
				if(Result == FFT_KILL_CONSOLE) {
					threadParams->pConsole->bKill = FF_TRUE;
					//break;
				}
			} else {
				Result = 0;
				fprintf(threadParams->pConsole->pStdOut, "A command was found, but no registered executable address was found.\n");
			}

			if(Result) {
				if(pCommand->ErrTable) {
					fprintf(threadParams->pConsole->pStdOut, "Command returned with message: \"%s\"\n", FFTerm_LookupErrMessage(pCommand->ErrTable, Result));
				} else {
					fprintf(threadParams->pConsole->pStdOut, "Command returned with Code (%ld)\n", Result);
				}
			}
		}
	} else {
		
		if(Error == FFT_ERR_CMD_NOT_FOUND) {
			if(strlen(threadParams->strCommandLine) > 0) {
				fprintf(threadParams->pConsole->pStdOut, "'%s' is not recognised as an internal or external command.\n", threadParams->pArgs[0]);
			}
		} else {
			if(strlen(threadParams->strCommandLine) > 0) {
				fprintf(threadParams->pConsole->pStdOut, "%s\n", FFTerm_GetErrMessage(Error));
			}
		}
	}

	threadParams->retVal = Result;

	pJobEntry->bExecuted = FF_TRUE;
	return Result;
}

FFT_BGJOBS *FFTerm_GetJobs() {
	return g_oJobsTable;
}

static FFT_BGJOBS *FFTerm_CreateJob() {
	FF_T_UINT8 i;
	FFT_BGJOBS *pJobEntry = NULL;

	if(!bJobsTableInit) {
		memset(g_oJobsTable, 0, sizeof(FFT_BGJOBS) * FFT_MAX_BG_JOBS);
		bJobsTableInit = FF_TRUE;
	}

	for(i = 0; i < FFT_MAX_BG_JOBS; i++) {
		if(g_oJobsTable[i].bInUse == FF_FALSE) {
			pJobEntry = &g_oJobsTable[i];
			pJobEntry->bInUse = 1;
			break;
		}
	}

	pJobEntry->usPid = g_usPid++;

	return pJobEntry;
}

FF_T_SINT32 FFTerm_ExecCommandInBG(FFT_CONSOLE *pConsole, FF_T_INT8 *strCommand) {
	FFT_BGJOBS *pJobEntry;

	pJobEntry = FFTerm_CreateJob();
	if(!pJobEntry) {
		fprintf(pConsole->pStdOut, "Error: Max Jobs Exceeded\n");
		return -1;
	}

	pJobEntry->bInUse = FF_TRUE;
	strcpy(pJobEntry->threadParams.strCommandLine, strCommand);
	pJobEntry->threadParams.pConsole = pConsole;

	fprintf(pConsole->pStdOut, "[%d] Job Created - \"%s\"\n", (unsigned int) pJobEntry->usPid, pJobEntry->threadParams.strCommandLine);

	if(pConsole->pSpec && pConsole->pSpec->pfnCreateBgThread) {
		pConsole->pSpec->pfnCreateBgThread((FFT_THREADROUTINE)bgThread, (void *) pJobEntry);
	} else {
		fprintf(pConsole->pStdOut, "Jobs not supported (&)\n");
	}

	return 0;	
}

FF_T_UINT32	FFTerm_GetConsoleWidth(FFT_CONSOLE *pConsole) {
	if(pConsole->pSpec && pConsole->pSpec->pfnGetConsoleWidth) {
		return pConsole->pSpec->pfnGetConsoleWidth();
	}
	return FFT_CONSOLE_WIDTH;
}

FF_T_UINT32 FFTerm_GetConsoleHeight(FFT_CONSOLE *pConsole) {
	if(pConsole->pSpec && pConsole->pSpec->pfnGetConsoleHeight) {
		return pConsole->pSpec->pfnGetConsoleHeight();
	}
	return FFT_CONSOLE_HEIGHT;
}

void FFTerm_SetConsoleColour(FFT_CONSOLE *pConsole, FF_T_UINT16 ulColour) {
	if(pConsole->pSpec && pConsole->pSpec->pfnSetConsoleColour) {
		pConsole->pSpec->pfnSetConsoleColour(ulColour);
	}
}

/**
 *	@public
 *	@brief	Kills the specified Console.
 *
 *	Allows another thread to Kill the specified console.
 *
 *	@param	pConsole		The FFT_CONSOLE object pointer.
 *
 *	@return	FFT_ERR_NONE on Success.
 **/
FF_T_SINT32 FFTerm_KillConsole(FFT_CONSOLE *pConsole) {

	if(!pConsole) {
		return FFT_ERR_NULL_POINTER;
	}

	pConsole->bKill = FF_TRUE;

	return FFT_ERR_NONE;
}

/**
 *	@private
 **/
static FFT_COMMAND *FFTerm_GetCmd(FFT_CONSOLE *pConsole, const FF_T_INT8 *pa_cmdName, FF_T_SINT32 *pError) {
	
	FFT_COMMAND *pCommand;
	FF_T_INT8	pa_cmdLower[FFT_MAX_CMD_NAME + 1];
	FF_T_INT8	cmdLower[FFT_MAX_CMD_NAME + 1];

	if(pError) {
		*pError = FFT_ERR_NONE;
	}
	
	if(!pConsole) {
		if(pError) {
			*pError = FFT_ERR_NULL_POINTER;
		}
		return NULL;
	}

	if(strlen(pa_cmdName) > (FFT_MAX_CMD_NAME)) {
		if(pError) {
			*pError = FFT_ERR_CMD_NOT_FOUND;
		}
		return NULL;
	}

	strcpy(pa_cmdLower, pa_cmdName);
	FFTerm_tolower(pa_cmdLower);		// Remove case from input command name.

	pCommand = pConsole->pCommands;

	if(pCommand) {
		while(pCommand != NULL) {
			strcpy(cmdLower, pCommand->cmdName);
			FFTerm_tolower(cmdLower);
			if(strcmp(cmdLower, pa_cmdLower) == 0) {
				return pCommand;
			}
			pCommand = pCommand->pNextCmd;
		}
	}

	if(pError) {
		*pError = FFT_ERR_CMD_NOT_FOUND;
	}

	return (FFT_COMMAND *) NULL;
}

/**
 *	@private
 **/
static FF_T_BOOL FFTerm_isSpace(FF_T_INT8 c) {
	if(c == ' ' || c == '\t' || c == '\n') {
		return FF_TRUE;
	}

	return FF_FALSE;
}

/**
 *	@private
 **/
static FF_T_SINT32 FFTerm_GenerateArguments(FF_T_INT8 *strCmdLine, FF_T_INT8 **ppArgv) {
	FF_T_UINT32	iArgc;		// Argument Counter
	FF_T_UINT32	iCmdLineLen	= strlen(strCmdLine);
	FF_T_INT8	*pCmdLineEnd = (strCmdLine +iCmdLineLen);		// Pointer to the end of the CmdLine
	
	for(iArgc = 0; iArgc < FFT_MAX_CMD_LINE_ARGS;) {
		// Skip past any white space at the beginning.
		while((strCmdLine < pCmdLineEnd) && (FFTerm_isSpace(*strCmdLine))) {
			strCmdLine++;
		}

		ppArgv[iArgc] = strCmdLine;
		
		// Allow arguments with spaces in them via inverted commas
		if(*strCmdLine == '\"') {
			strCmdLine++;
			ppArgv[iArgc] = strCmdLine;
			while((*strCmdLine) && (*strCmdLine != '\"')) {
				strCmdLine++;
			}
			// Skip past the " character
			*strCmdLine = '\0';
			strCmdLine++;
		}

		while((*strCmdLine) && (!FFTerm_isSpace(*strCmdLine))) {
			strCmdLine++;
		}

		*strCmdLine = '\0';
		strCmdLine++;

		// Check for end of command line.

		if(strCmdLine >= pCmdLineEnd) {
			break;
		} else {
			iArgc++;
		}
	}

	iArgc++;
	if(iArgc > FFT_MAX_CMD_LINE_ARGS) {
		iArgc = FFT_MAX_CMD_LINE_ARGS;
	}

	return iArgc;
}

FF_T_BOOL FFTerm_wildcompare(const FF_T_INT8 *pszWildCard, const FF_T_INT8 *pszString) {
    register const FF_T_INT8 *pszWc 	= NULL;
	register const FF_T_INT8 *pszStr 	= NULL;	// Encourage the string pointers to be placed in memory.
    do {
        if ( *pszWildCard == '*' ) {
			while(*(1 + pszWildCard++) == '*'); // Eat up multiple '*''s
			pszWc = (pszWildCard - 1);
            pszStr = pszString;
        }
		if (*pszWildCard == '?' && !*pszString) {
			return FF_FALSE;	// False when the string is ended, yet a ? charachter is demanded.
		}
#ifdef FF_WILDCARD_CASE_INSENSITIVE
        if (*pszWildCard != '?' && tolower(*pszWildCard) != tolower(*pszString)) {
#else
		if (*pszWildCard != '?' && *pszWildCard != *pszString) {
#endif
			if (pszWc == NULL) {
				return FF_FALSE;
			}
            pszWildCard = pszWc;
            pszString = pszStr++;
        }
    } while ( *pszWildCard++ && *pszString++ );

	while(*pszWildCard == '*') {
		pszWildCard++;
	}

	if(!*(pszWildCard - 1)) {	// WildCard is at the end. (Terminated)
		return FF_TRUE;	// Therefore this must be a match.
	}

	return FF_FALSE;	// If not, then return FF_FALSE!
}

FF_T_SINT32 FFTerm_TabulateAction(FFT_CONSOLE *pConsole, FF_T_INT8 *strCommandLine, FFT_TAB_CONTEXT *pTabCtx) {
	int i,y = 0;
	int bFirstToken = 0;
	int bEmptyToken = 0;
	char token[FFT_MAX_CMD_NAME];
	char *p;

	FFT_COMMAND *pCommand;
	
	if(pConsole->pTabulate) {
		strCommandLine;
	}

	i = strlen(strCommandLine);

	while(FFTerm_isSpace(*strCommandLine)) {
		strCommandLine++;
	}
	
	if(!*strCommandLine) {
		bEmptyToken = 1;
	}

	if(bEmptyToken) {
		pCommand = pConsole->pCommands;

		printf("\n");

		do {
			fprintf(pConsole->pStdOut, "%-10s", pCommand->cmdName);
			pCommand = pCommand->pNextCmd;
		} while(pCommand);

		printf("\n");

		return -2;
	}

	// Partially completed token, any matches?
	p = token;
	while(!FFTerm_isSpace(*strCommandLine) && *strCommandLine != '\0') {
		*p++ = *strCommandLine;
		strCommandLine++;
	}

	*p++ = '*';
	*p = '\0';


	// Now we have the first token, is it the only token?
	while(FFTerm_isSpace(*strCommandLine)) {
		strCommandLine++;
	}

	if(!strCommandLine[0]) {
		bFirstToken = 1;
	}


	if(bFirstToken) {
		// Find a suitable internal command.

		if(pTabCtx->origToken[0]) {
			 memset(token, 0, sizeof(token));
			 strcpy(token, pTabCtx->origToken);		     
		}

		pCommand = pConsole->pCommands;
		do {

			if(FFTerm_wildcompare(token, pCommand->cmdName)) {
				
				if(y == pTabCtx->tab_interation) {
					if(pTabCtx->tab_interation == 0) {
						for(i = 0; i < (int)(strlen(token)-1); i++) {
							printf("\b \b");
						}
					} else {
						for(i = 0; i < pTabCtx->charsAdded+pTabCtx->charsToRemove; i++) {
							printf("\b \b");
						}
					}
					printf("%s", pCommand->cmdName);
					pTabCtx->charsToRemove = strlen(token)-1;
					pTabCtx->charsAdded = strlen(pCommand->cmdName) - pTabCtx->charsToRemove;
					strcpy(pTabCtx->lastToken, pCommand->cmdName);
					if(pTabCtx->tab_interation == 0) {
						strcpy(pTabCtx->origToken, token);
					}
					pTabCtx->tab_interation += 1;

					if(pTabCtx->tab_interation == 1) {
						return -3;
					}

					return -4;
				}

				y++;
			}

			pCommand = pCommand->pNextCmd;
		} while(pCommand);

		/*if(y == pTabCtx->tab_interation) {
			pTabCtx->tab_interation = 0;
			}*/

	} else {
		// Pass tabulation onto a tab-agent for a specific command.

	}

	return 0;
}

FF_T_SINT32 FFTerm_HookTabulateFunctions(FFT_CONSOLE *pConsole, FFT_TABULATE *pTabulate) {
	if(pConsole) {
		pConsole->pTabulate = (FFT_TABULATE *) malloc(sizeof(FFT_TABULATE));
		if(!pConsole->pTabulate) {
			return FFT_ERR_NOT_ENOUGH_MEMORY;
		}

		pConsole->pTabulate->fnTabFirst = pTabulate->fnTabFirst;
		pConsole->pTabulate->fnTabNext 	= pTabulate->fnTabNext;
		pConsole->pTabulate->fnTabClose	= pTabulate->fnTabClose;

		return FFT_ERR_NONE;
	}

	return FFT_ERR_NULL_POINTER;
}

/**
 *	@private
 **/
static FF_T_SINT32 FFTerm_GetCommandLine(FFT_CONSOLE *pConsole, FF_T_INT8 *strCommandLine, FF_T_UINT32 ulLimit) {
	FF_T_UINT32	i = 0;
	FF_T_SINT32 y;
	FF_T_INT8	*pBuf = strCommandLine;
	FF_T_INT32	c;
	FF_T_BOOL	bBreak = FF_FALSE;
	FF_T_BOOL	bDoubleTabbed = 0;
	FF_T_BOOL	bWordCompleted = 0;
	FF_T_BOOL	bInTab = 0;
	FF_T_BOOL	bBeginWord = 1;

	int bsChar = FFT_ESCAPE, tabChar = FFT_TAB, escapeChar = FFT_ESCAPE, retChar = FFT_RETURN;

	FFT_TAB_CONTEXT	tabCtx;

	if(pConsole->pSpec && pConsole->pSpec->pCustomParams) {
		bsChar		= pConsole->pSpec->pCustomParams->ucBackspaceChar;
		tabChar		= pConsole->pSpec->pCustomParams->ucTabChar;
		escapeChar	= pConsole->pSpec->pCustomParams->ucEscapeChar;
		retChar		= pConsole->pSpec->pCustomParams->ucReturnChar;
	}

	memset(&tabCtx, 0, sizeof(tabCtx));

	strCommandLine[0] = '\0';

	while(!bBreak) {
		if((c = fgetc(pConsole->pStdIn)) >= 0) {
			
			if(c == retChar) {
				if((pConsole->ulMode & (FFT_ENABLE_ECHO_INPUT | FFT_ENABLE_WINDOWS))) {
					 //fputc('\n', pConsole->pStdOut);
					if(pConsole->pSpec && pConsole->pSpec->pCustomParams) {
						fputs(pConsole->pSpec->pCustomParams->szpCrLf, pConsole->pStdOut);
					} else {
						fputs(FFT_CRLF, pConsole->pStdOut);
					}
				}

				pBuf[i] = '\0';
				bBreak = FF_TRUE;
				break;
			} else if(c == _FFTERM_LF_CHAR_) {

			} else if(c == bsChar) {
				if(i > 0) {
					fprintf(pConsole->pStdOut, "\b \b");
					i--;
					pBuf--;
					*pBuf = '\0';
				}
			} else if(c == escapeChar) {
				fprintf(pConsole->pStdOut, "^ESC");
			} else if(c == tabChar) {
				// Try to tabulate the commandline.
				if(!bDoubleTabbed && bBeginWord && i == 0) {

					bDoubleTabbed = 1;
					break;
				}

				bDoubleTabbed = 0;

				y = FFTerm_TabulateAction(pConsole, strCommandLine, &tabCtx);

				if(y == -3) {
					 bInTab = 1;
					 tabCtx.iOrginalTokenPos = i-tabCtx.charsToRemove;;
					 strcpy(&strCommandLine[tabCtx.iOrginalTokenPos], tabCtx.lastToken);
					 i = tabCtx.iOrginalTokenPos + strlen(tabCtx.lastToken);
				}

				if(y == -4) {
					 bInTab = 1;
					 strcpy(&strCommandLine[tabCtx.iOrginalTokenPos], tabCtx.lastToken);
					 i = tabCtx.iOrginalTokenPos + strlen(tabCtx.lastToken);
				}

				if(y == -2) {
					strCommandLine[0] = '\0';
					bBreak = FF_TRUE;

					if((pConsole->ulMode & (FFT_ENABLE_ECHO_INPUT | FFT_ENABLE_WINDOWS))) {
						fputc('\n', pConsole->pStdOut);
					}
				}

			} else {
				if(i < (ulLimit - 1)) {

					 if(bInTab) {
						  //i += (tabCtx.charsAdded);
						  bInTab = 0;
						  pBuf += strlen(tabCtx.lastToken)-1;
					 }

					*(pBuf++) = (FF_T_INT8) c;
					*pBuf = '\0';
					if((pConsole->ulMode & FFT_ENABLE_ECHO_INPUT)) {
						fputc(c, pConsole->pStdOut);
					}

					i++;
				}
			}
		} else {
			//Sleep(10);
			i= i;
			//FFTerm_Sleep(10);	// Sleep for 10 milliseconds!
			// A sleep here can be used to prevent a wasteful loop.
		}
	}
	
	return FFT_ERR_NONE;
}


/**
 *	@private
 **/
static FF_T_SINT32 FFTerm_PrintPrompt(FFT_CONSOLE *pConsole) {
	FFT_COMMAND *pCommand;
	pCommand = FFTerm_GetCmd(pConsole, "prompt", NULL);
	if(pCommand) {
		if(pCommand->fnCmd || pCommand->fnCmdEx) {
			if(pCommand->fnCmd) {
				pCommand->fnCmd(0, NULL);
			}
			if(pCommand->fnCmdEx) {
				pCommand->fnCmdEx(0, NULL, pCommand->CmdExParam);
			}
		}
	} else {
		fprintf(pConsole->pStdOut, "%s", pConsole->strCmdPrompt);
	}
	return FFT_ERR_NONE;
}



/**
 *	@private
 **/
const FF_T_INT8 *FFTerm_LookupErrMessage(const FFT_ERR_TABLE *pa_pErrTable, FF_T_SINT32 iErrorCode) {
	const FFT_ERR_TABLE *pErrTable = pa_pErrTable;
	while (pErrTable->strErrorString){
        if (pErrTable->iErrorCode == iErrorCode) {
            return pErrTable->strErrorString;
        }
		pErrTable++;
    }
	return pa_pErrTable->strErrorString;
}


/**
 *	@private
 **/
FF_T_SINT32	FFTerm_SortCommands(FFT_CONSOLE *pConsole) {
	FFT_COMMAND *pCommand = pConsole->pCommands;
	FFT_COMMAND *pCmd1, *pCmd2;
	FF_T_UINT32	iSwaps;
	FF_T_SINT32	iResult;

	FF_T_INT8	CmdLower[FFT_MAX_CMD_NAME + 1];
	FF_T_INT8	NextCmdLower[FFT_MAX_CMD_NAME + 1];

	do {
		iSwaps = 0;
		pCommand = pConsole->pCommands;
		while(pCommand && pCommand->pNextCmd) {
			strcpy(CmdLower, pCommand->cmdName);
			strcpy(NextCmdLower, pCommand->pNextCmd->cmdName);
			FFTerm_tolower(CmdLower);
			FFTerm_tolower(NextCmdLower);
			iResult = strcmp(CmdLower, NextCmdLower);
			if(iResult == 1) {	// Put pCommand after the next command.
				iSwaps++;
				pCmd1 = pConsole->pCommands;
				if(pCmd1 == pCommand) {
					pCmd2 = pCommand->pNextCmd;
					pConsole->pCommands = pCmd2;
					pCommand->pNextCmd = pCmd2->pNextCmd;
					pCmd2->pNextCmd = pCommand;
				} else {
					while(pCmd1 && pCmd1->pNextCmd) {
						if(pCmd1->pNextCmd == pCommand) {
							pCmd2 = pCommand->pNextCmd;
							pCmd1->pNextCmd = pCmd2;
							pCommand->pNextCmd = pCmd2->pNextCmd;
							pCmd2->pNextCmd = pCommand;
							break;
						}
						pCmd1 = pCmd1->pNextCmd;
					}
				}

			}
			if(iResult == -1) {	// Leave current string where it is.
				

			}
			pCommand = pCommand->pNextCmd;
		}
	} while(iSwaps);
	
	return FFT_ERR_NONE;
}

static void FFTerm_tolower(FF_T_INT8 *string) {
	while(*string) {
		if(*string >= 'A' && *string <= 'Z') {
			*string += ('a' - 'A');		// Difference between the capital and small case letters.
		}
		string++;
	}
}

// FFTerm getopt (Commandline processing routines).

const char *FFTerm_getarg(int argc, const char **argv, int argindex, FFT_GETOPT_CONTEXT *ctx) {
	int i;
	int argCount = 0;
	int bAltered = 0;

	if(ctx->optind == 0) {
		ctx->optind = 1;
		bAltered = 1;
	}

	for(i = ctx->optind; i < argc; i++) {
		if(argv[i][0] != '-') {
			if(argCount == argindex) {
				return argv[i];
			}
			argCount++;
		}
	}

	if(bAltered) {
		ctx->optind = 0;
	}
	
	return NULL;
}




/*
	Iterates through commandline Arguments.
	See the documentation for GNU's getopt() function. This has the same behaviour,
	except all information is provided in the ctx pointer, and not globally.
*/
int FFTerm_getopt(int argc, const char **argv, const char *optstring, FFT_GETOPT_CONTEXT *ctx) {
	int i, swaps;
	const char *pc;
	const char *argvSwap;

	// Sort through the commandline arguments, and re-arrange them. So all options come first.
	do {
		swaps = 0;
		for(i = 1; i < argc; i++) {
			// Ensure we skip past option arguments!
			if(!swaps && argv[i][0] == '-') {
				pc = strchr(optstring, argv[i][1]);	// argv[i] is now the option string, lets see if it had a compulsory argument?
				if(pc) {
					if(*(pc + 1) == ':') {
						if(!argv[i][2] && (i + 1) < argc && argv[i + 1][0] != '-') { // If argv[i][3] is no \0 then do nothing! The param is connected.
							// Swap the next param's
							i++;	// Ensure we don't swap an -option back over this argument!
						}
					}
				}
			} else {
			
				if(argv[i][0] != '-' && ((i + 1) < argc)) {
					if(argv[i + 1][0] == '-') {
						// Swap Option!
						argvSwap = argv[i];
						argv[i] = argv[i + 1];
						argv[i + 1] = argvSwap;

						// Detect if option has an argument, this must be swapped too!
						pc = strchr(optstring, argv[i][1]);	// argv[i] is now the option string, lets see if it had a compulsory argument?
						if(pc) {
							if(*(pc + 1) == ':') {
								if(!argv[i][2] && (i + 2) < argc && argv[i + 2][0] != '-') { // If argv[i][3] is no \0 then do nothing! The param is connected.
									// Swap the next param's
									argvSwap = argv[i + 1];
									argv[i + 1] = argv[i + 2];
									argv[i + 2] = argvSwap;
									i++;	// Ensure we don't swap an -option back over this argument!
								}
							}
						}
						swaps++;

					}
				}
			}
		}
	} while(swaps);

	if(ctx->optind == 0) {
		ctx->optind = 1;	// argv[0] is of no interest!
	}
	
	for(i = ctx->optind; i < argc; i++) {

		if(ctx->nextchar) {
			if(argv[i][ctx->nextchar+1] == '\0') {
				i++;
				ctx->optind = i;
				ctx->nextchar = 0;
			}
		}

		if(i >= argc) {
			break;
		}

		if(argv[i][0] == '-' && !(argv[i][1] == '-' || argv[i][1] == '\0')) {
			// A single option argument was found, now process!

			pc = strchr(optstring, argv[i][ctx->nextchar + 1]);
			if(pc) {
				if(*(pc + 1) == ':') {
					if(ctx->nextchar) {	// This kind of option must come on its own
						ctx->optopt = argv[i][ctx->nextchar + 1];
						ctx->nextchar++;
						return '?';
					}
					// Option Argument
					if(argv[i][2]) {
						ctx->optarg = &argv[i][2];
						if(ctx->optarg[0] == '-') {
							ctx->optarg = NULL;
							ctx->nextchar = 0;
							ctx->optind = i+1;
							return ':';
						}
					} else {
						ctx->optarg = argv[i + 1];
						if((i+1) >= argc) {
							ctx->optarg = NULL;
							ctx->nextchar = 0;
							ctx->optind = i+1;
							return ':';
						}
						ctx->optind = i+2; // Skip over this argument next time.
						ctx->nextchar = 0;
						return argv[i][1];
					}
					ctx->optind = i+1;
					ctx->nextchar = 0;

					return argv[i][1];

				} else {
					ctx->optarg = NULL;
				}
				//ctx->optind = i+1;
				ctx->nextchar++;
				return argv[i][ctx->nextchar];
			} else {
				//ctx->optind += 1;
				ctx->optopt = argv[i][ctx->nextchar + 1];
				ctx->nextchar++;
				return '?';
			}
		} else if(argv[i][0] != '-') {
			// End of optional arguments, simply return EOF, with optind set to i;
			ctx->optind = i;
			return EOF;
		}

		ctx->optind += 1;
	}

	if(ctx->optind >= argc) {
		ctx->optind = 0;
	}

	return EOF;
}


/**
	Only process long options!
**/
/*
int FF_getopt_long_only(int argc, const char **argv, const char *optstring, FFT_GETOPTLONG_CONTEXT *ctx) {
	int i;
	struct option *pOptions = ctx->longopts;
	
	for(i = ctx->optind; i < argc; i++) {
		if(argv[i][0] == '-' && argv[i][1] == '-') {	// Identified a long option!
			// Does this long option match one in the options list?
			while(pOptions) {
				if(!strcmp(pOptions->name, &argv[i][2])) {	// Matches this item!
					ctx->longindex_val = (int) ((pOptions - ctx->longopts) / sizeof(struct option));
					if(ctx->longindex) {
						*ctx->longindex = (int) (pOptions - ctx->longopts);
					}

					// Check for parameters
					switch(pOptions->has_arg) {
						case 0:	// No argument
							ctx->optind = i;
							if(!pOptions->flag) {
								return pOptions->val;
							} else {
								*pOptions->flag = pOptions->val;
								return 0;
							}
							break;
						case 1:	// Compulsory argument
							if(argv[i][3] == '=') {
								ctx->optarg = &argv[i][4];							
							} else {
								ctx->optarg = argv[i + 1];
								ctx->optind = i+1;
							}
							break;
						case 2:	// Optional argument
							break;
					}


				} else {
					// Unrecognised Long option!
					ctx->optind = i;
					return '?';
				}

				pOptions++;
			}

			// Option unrecognised!
			return '?';
		}
	}

	return EOF;
}
*/

/**
	As above, except we process long arguments as well!
**//*
int FF_getopt_long(int argc, const char **argv, const char *optstring, FF_GETOPTLONG_CONTEXT *ctx) {
	int i;
	const char *pc;

	for(i = ctx->optind; i < argc; i++) {

		if(ctx->nextchar) {
			if(argv[i][ctx->nextchar + 1] == '\0') {
				i++;
				ctx->optind = i;
				ctx->nextchar = 0;
			}
		}

		if(i >= argc) {
			break;
		}

		if(argv[i][0] == '-' && !(argv[i][1] == '-' || argv[i][1] == '\0')) {
			// A single option argument was found, now process!

			pc = strchr(optstring, argv[i][ctx->nextchar + 1]);
			if(pc) {
				if(*(pc + 1) == ':') {
					if(ctx->nextchar) {	// This kind of option must come on its own
						ctx->optopt = argv[i][ctx->nextchar + 1];
						ctx->nextchar++;
						return '?';
					}
					// Option Argument
					if(argv[i][2]) {
						ctx->optarg = &argv[i][2];
						if(ctx->optarg[0] == '-') {
							ctx->optarg = NULL;
							ctx->nextchar = 0;
							ctx->optind = i+1;
							return ':';
						}
					} else {
						ctx->optarg = argv[i + 1];
						if((i+1) >= argc) {
							ctx->optarg = NULL;
							ctx->nextchar = 0;
							ctx->optind = i+1;
							return ':';
						}
					}
					ctx->optind = i+1;
					ctx->nextchar = 0;

					return argv[i][1];

				} else {
					ctx->optarg = NULL;
				}
				//ctx->optind = i+1;
				ctx->nextchar++;
				return argv[i][ctx->nextchar];
			} else {
				//ctx->optind += 1;
				ctx->optopt = argv[i][ctx->nextchar + 1];
				ctx->nextchar++;
				return '?';
			}
		}

		ctx->optind += 1;
	}

	//ctx->bDone 	= 1;
	ctx->optind = 0;

	return EOF;
}
*/
